package graphics;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.swing.JPanel;

import org.apache.commons.collections15.Transformer;

import plots.Edge;
import plots.Plot;
import strategies.PlotTarget;
import strategies.Strategy;
import edu.uci.ics.jung.algorithms.layout.CircleLayout;
import edu.uci.ics.jung.algorithms.layout.FRLayout;
import edu.uci.ics.jung.algorithms.layout.FRLayout2;
import edu.uci.ics.jung.algorithms.layout.ISOMLayout;
import edu.uci.ics.jung.algorithms.layout.KKLayout;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.layout.SpringLayout;
import edu.uci.ics.jung.algorithms.layout.SpringLayout2;
import edu.uci.ics.jung.visualization.BasicVisualizationServer;

import agents.Agent;
import base.Environment;
import base.Environment.Configurations;

public class GraphView {
	
	private Environment env;
	private int animSpeed;
	private Dimension graphDim;
	private JPanel currPanel;
	
	//	Graph Layout Elements
	private Layout<Plot, Edge> layout=null;
	private BasicVisualizationServer<Plot, Edge> vv;
	
	//	Plot visual elements
	private final double MIN_DIAMETER = 20;	
	private final double MAX_DIAMETER = 50;
	private final double MIN_WIDTH = 1;
	private final double MAX_WIDTH =15;
	
	private double regenThr; 
	

	
	protected GraphView(Environment env, int animSpeed){
		this.animSpeed = animSpeed;
		this.env = env;
		this.graphDim = graphDim;
		regenThr = env.getDouble(Configurations.REGEN_THRESHOLD);
	}
	
	protected JPanel getGraphView(Dimension graphDim){
		currPanel = new JPanel();
		
		int typeOLayout = env.getInt(Configurations.GRAPH_LAYOUT);
		
		/*
		 * Just messing around with different type of layouts
		 */
		switch (typeOLayout) {
		case 1:
			layout = new CircleLayout<Plot, Edge>(env.getPlots());
			break;
		case 2:
			layout = new FRLayout<Plot, Edge>(env.getPlots());
			break;
		case 3:
			layout = new FRLayout2<Plot, Edge>(env.getPlots());
			break;
		case 4:
			layout = new ISOMLayout<Plot, Edge>(env.getPlots());
			break;
		case 5:
			layout = new KKLayout<Plot, Edge>(env.getPlots());
			break;
		case 6:
			layout = new SpringLayout<Plot, Edge>(env.getPlots());
			break;
		case 7:
			layout = new SpringLayout2<Plot, Edge>(env.getPlots());
			break;
		default:
			layout = new ISOMLayout<Plot, Edge>(env.getPlots());
			break;
		}
		
		layout.setSize(graphDim);		
		vv = new BasicVisualizationServer<Plot, Edge>(layout);		
		
		
		vv.setPreferredSize(graphDim);
		vv.getRenderContext().setVertexShapeTransformer(getShapes());
		vv.getRenderContext().setEdgeStrokeTransformer(getStrokes());
		resetColour();
		
		currPanel.add(vv);

		return currPanel;
	}
	
	protected void setAnimationSpeed(int animSpeed){
		this.animSpeed = 20 * animSpeed;		
	}
	
	  // Transformer maps the vertex number to a vertex property
    Transformer<Plot,Paint> vertexColor = new Transformer<Plot, Paint>() {
        public Paint transform(Plot i) {
            int red = (int)(((double)i.hashCode()/5d) * 255d);            	
        	return new Color(red, 255-red, 0);
        }
    };
    
    /**
     * Parallel mode; this is just as to see how the agents distribute their farm;
     * It is not an accurate depiction on how the agent functions
     */
    protected void walkAgents(){
    	env.regenerate();
    	
    	List<Agent> agents = env.getActiveAgents(); 
    	int maxSteps = 0;    	
    	for (Agent x:agents){
    		int stratSize = x.getLastPlayedStrategy().getPath().size(); 
    		maxSteps = (maxSteps>stratSize? maxSteps:stratSize);
    	}
    	
    	HashMap<Plot, ArrayList<String>> plotAgents = new HashMap<Plot, ArrayList<String>>();
    	for(Plot x:env.getPlots().getVertices())
    		plotAgents.put(x, new ArrayList<String>());
    	
		vv.getRenderContext().setVertexLabelTransformer(getPlotLabels(plotAgents));
		vv.getRenderContext().setVertexFillPaintTransformer(getPlotColours(null, null));
		currPanel.repaint();
		framePause(2);
    	
    	for(int i=0; i<maxSteps; i++){
        	for(Plot x:env.getPlots().getVertices())
        		plotAgents.put(x, new ArrayList<String>());
    		for(Agent x:agents){
    			LinkedList<PlotTarget> path = x.getLastPlayedStrategy().getPath();
    			
    			
    			if (path.size() > i){
    				Plot visPlot = path.get(i).plot;
    				String agLabel = "A" + x.getID();     				
    				plotAgents.get(visPlot).add(agLabel);
    				visPlot.setYield(visPlot.getYield() - path.get(i).target);    				
    			}
    		}
    		vv.getRenderContext().setVertexLabelTransformer(getPlotLabels(plotAgents));
    		vv.getRenderContext().setVertexFillPaintTransformer(getPlotColours(null, null));
    		currPanel.repaint();
    		framePause(2);
    	}    	
    }
    
    
    protected void animateAgent(Agent x){
    	LinkedList<PlotTarget> path = x.getLastPlayedStrategy().getPath();
    	List<Plot> visitedPlots = new ArrayList<Plot>();
    	List<Edge> visitedEdges = new ArrayList<Edge>();
    	Plot prevPlot = null;
    	
    	for(PlotTarget y:path){
    		if (prevPlot != null){
    			Edge toColour = env.getPlots().findEdge(prevPlot, y.plot);
    			visitedEdges.add(toColour);
    			vv.getRenderContext().setEdgeDrawPaintTransformer(getEdgeColours(visitedEdges, toColour));
    			
    			currPanel.repaint();
    			framePause(1);
    		} else {
    			vv.getRenderContext().setEdgeDrawPaintTransformer(new Transformer<Edge, Paint>() {					
					public Paint transform(Edge arg0) {
						return Color.BLACK;
					}
				});
    		}
    		
    		visitedPlots.add(y.plot);
    		prevPlot = y.plot;
    		vv.getRenderContext().setVertexFillPaintTransformer(getPlotColours(visitedPlots, y.plot));
    		currPanel.repaint();
    		framePause(1);
    	}
    }
	
	protected void resetColour(){
		
		vv.getRenderContext().setVertexFillPaintTransformer(new Transformer<Plot, Paint>() {
	        public Paint transform(Plot i) {
	        	
	        	return Color.GREEN;
	        }
		});
		currPanel.repaint();
	}
	
	//	Set Circle size to reflect it's richness; bigger is richer
	private Transformer<Plot, Shape> getShapes(){

	    double averageR = 0, minR = Double.MAX_VALUE;
		
	    for (Plot x:env.getPlots().getVertices()){
	    	averageR += x.getMaxYield();
	    	if(x.getMaxYield() < minR)
	    		minR = x.getMaxYield();	    	
		}
	    
	    averageR /= env.getPlots().getVertexCount();
	    
	    final double averageRfinal = averageR;
	    final double minRfinal = minR;	    
	    
		Transformer<Plot,Shape> result = new Transformer<Plot,Shape>(){
	    	public Shape transform(Plot i){
	            Shape result=null;
	    		double medDiam = (MIN_DIAMETER + MAX_DIAMETER)/2;
	    		
	    		if (i.getMaxYield() == averageRfinal )
	    			result = new Ellipse2D.Double(-15, -15, medDiam, medDiam);
	    		else{
	    			double diam = (Math.abs(averageRfinal - i.getMaxYield()) / (averageRfinal - minRfinal)) * (medDiam - MIN_DIAMETER);
	    			if (i.getMaxYield() < averageRfinal)
	    				diam = medDiam - diam;
	    			else
	    				diam = medDiam + diam;
	    			
	    			result = new Ellipse2D.Double(-15, -15, diam, diam);
	    		}
	    		
	    		return result;
	    	}
	    };
	    
	    return result;
	}
	
	//	Set edge size to reflect penalty; bigger is smaller penalty (easier to access)
	private Transformer<Edge, Stroke> getStrokes(){
		
		double minE = Double.MAX_VALUE;
		double maxE = 0;
		
		for (Edge x:env.getPlots().getEdges()){
			if (x.getValue() < minE)
				minE = x.getValue();
			if (x.getValue() > maxE)
				maxE = x.getValue();
		}
		
		final double minEfin = minE, maxEfin = maxE;
		
		Transformer<Edge, Stroke> result = new Transformer<Edge, Stroke>() {

			@Override
			public Stroke transform(Edge arg0) {
				double perc = (arg0.getValue() - minEfin)/(maxEfin -minEfin);
				double width = MIN_WIDTH + perc * (MAX_WIDTH - MIN_WIDTH);
				return new BasicStroke((int)width);
			}
		}; 
			
			
			
		return result;
	}

	private Transformer<Plot, Paint> getPlotColours(List<Plot> roadPlots, Plot currPlot){
		final List<Plot> rPlots = roadPlots;
		final double regenThr = this.regenThr;
		final Plot cPlot = currPlot;
		
		Transformer<Plot,Paint> result = new Transformer<Plot,Paint>(){
	    	public Paint transform(Plot i){	            
	    		
	            if (cPlot == i)
	            	return Color.BLUE;
	    		if (rPlots!=null && rPlots.contains(i))
	            	return Color.GRAY;
	           
	            double percentageFree = i.getYield() / i.getMaxYield();	            
	            
	            if (percentageFree > regenThr){
	            	double fairAmount = (int)((1-regenThr)*i.getMaxYield());
	            	int red = (int)((i.getMaxYield() - i.getYield())/fairAmount * 255); 
	            	System.out.println("red is "+red);
	            	return new Color(red,255-red,0);
	            } else {
	            	int red = (int)(percentageFree/regenThr * 125d);
	            	return new Color(red, 0, 0);
	            }
	    	}
	    };		
		
		return result;
	}
	
	private Transformer<Edge, Paint> getEdgeColours(List<Edge> roadEdges, Edge currEdge){
		final List<Edge> rEdges = roadEdges;
		final Edge cEdge = currEdge;
		
		Transformer<Edge, Paint> result = new Transformer<Edge, Paint>() {

			@Override
			public Paint transform(Edge arg0) {
				if (arg0 == cEdge )
					return Color.BLUE;
				if (rEdges.contains(arg0))
					return Color.GRAY;
				return Color.BLACK;
			}
		};
		
		return result;
	}
	
	private Transformer<Plot, String> getPlotLabels(HashMap<Plot, ArrayList<String>> labels){
		final HashMap<Plot, ArrayList<String>> cLabels = labels;
		return new Transformer<Plot,String>(){
			@Override 
			public String transform(Plot arg0){
				String result = "";
				ArrayList<String> agents = cLabels.get(arg0);
				for(String x:agents)
					result += (x + "; "); 
				return result;
			}
		};
	}
	
	private void framePause(double factor){
		try {
			Thread.sleep((int)(factor * animSpeed));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
